#define LOG_TAG "ffmpeg_jni"
#define  LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define  LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__)
#define  LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)

#include <jni.h>
#include <android/log.h>

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <assert.h>

#include <android/bitmap.h>


#ifdef __cplusplus
extern "C" {
#endif
#define UINT64_C (unsigned long long)
#include "avcodec.h"
#include "avformat.h"
#include "swscale.h"

#ifdef __cplusplus
}
#endif

#include <stdio.h>


static const char* const OutOfResourcesException =
    "com/test/MediaPlayer/MediaPlayView$OutOfResourcesException";


static __attribute__((noinline))
void doThrow(JNIEnv* env, const char* exc, const char* msg = NULL)
{
    if (!env->ExceptionOccurred()) {
        jclass npeClazz = env->FindClass(exc);
        env->ThrowNew(npeClazz, msg);
    }
}


int player_nativeViewInit(JNIEnv * env, jobject  obj, jobject bitmap){
	int ret;
	AndroidBitmapInfo  info;
	if ((ret = AndroidBitmap_getInfo(env, bitmap, &info)) < 0) {
		LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret);
		return -1;
	}

	return 0;
}


int player_nativeViewRender(JNIEnv * env, jobject  obj, jobject bitmap){

    AndroidBitmapInfo  info;
	void*              pixels;
	int                ret;
	static int         init;
	int i=0;

	if(bitmap != NULL){

		if ((ret = AndroidBitmap_getInfo(env, bitmap, &info)) < 0) {
			LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret);
			return -1;
		}

		if (info.format != ANDROID_BITMAP_FORMAT_RGB_565) {
			LOGE("Bitmap format is not RGB_565 !");
			return -1;
		}

		if ((ret = AndroidBitmap_lockPixels(env, bitmap, &pixels)) < 0) {
			LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
		}

		LOGD("pixels=%p width=%d height=%d stride=%d",
			pixels, info.width, info.height, info.stride);

        // render here!


		AndroidBitmap_unlockPixels(env, bitmap);
		return 1;
	}

	return -1;
}


AVFormatContext *pFormatCtx;
AVCodecContext  *pCodecCtx;
AVCodec         *pCodec;
AVFrame         *pFrame;
AVPacket        packet;
int videoStream;

int jni_ffmpeg_init(JNIEnv* env, jobject obj){
	// Register all formats and codecs
	av_register_all();
	LOGD("== av_register_all ==");
	return 0;
}

int jni_ffmpeg_setupFile(JNIEnv* env, jobject obj, jstring filepath){

	const char *pathStr = env->GetStringUTFChars(filepath, NULL);

	if(av_open_input_file(&pFormatCtx, pathStr, NULL, 0, NULL)!=0)  {
		return -1; // Couldn't open file
	}
	return 0;
}

int jni_ffmpeg_startPlay(JNIEnv* env, jobject obj){
	int i;
	  // Retrieve stream information
	  if(av_find_stream_info(pFormatCtx)<0)
	  	return -1;

 	// Find the first video stream
 	videoStream=-1;
	for(i=0; i<pFormatCtx->nb_streams; i++)
		if(pFormatCtx->streams[i]->codec->codec_type==CODEC_TYPE_VIDEO) {
			videoStream=i;
			break;
		}

		if(videoStream==-1)
			return -1; // Didn't find a video stream

	// Get a pointer to the codec context for the video stream
	pCodecCtx=pFormatCtx->streams[videoStream]->codec;

	// Find the decoder for the video stream
	pCodec=avcodec_find_decoder(pCodecCtx->codec_id);
	if(pCodec==NULL) {
		LOGE("Unsupported codec!\n");
		return -1; // Codec not found
	}

	// Open codec
	if(avcodec_open(pCodecCtx, pCodec)<0)
		return -1; // Could not open codec

	// Allocate video frame
	pFrame=avcodec_alloc_frame();
}

int jni_ffmpeg_cleanup(JNIEnv* env, jobject obj){
    // Free the YUV frame
    av_free(pFrame);

    // Close the codec
    avcodec_close(pCodecCtx);

    // Close the video file
    av_close_input_file(pFormatCtx);

    jclass clazz = env->FindClass("test/freshui/ffmpeg/PlayerControl");
    jfieldID isExit = env->GetFieldID(clazz, "isExit", "Z"); // get ID from class
    env->SetBooleanField(obj, isExit, true);               // get field object from object

	return 0;
}

int jni_ffmpeg_stop(JNIEnv* env, jobject obj){
    jclass clazz = env->FindClass("test/freshui/ffmpeg/PlayerControl");
    jfieldID isDecodeID = env->GetFieldID(clazz, "needDecoding", "Z"); // get ID from class
    env->SetBooleanField(obj, isDecodeID, false);               // get field object from object

	return 0;
}

int jni_ffmpeg_updateSurface(JNIEnv* env, jobject obj, jobject argPlayerView){

    static struct SwsContext *img_convert_ctx;
    int ret;
    jclass viewClass = env->FindClass("test/freshui/ffmpeg/PlayerView");
    jfieldID bitmapID = env->GetFieldID(viewClass, "mBitmap", "Landroid/graphics/Bitmap;"); // get ID from class
    jobject bitmap = env->GetObjectField(argPlayerView, bitmapID);               // get field object from object
    AndroidBitmapInfo  info;
    void *pixels;
    AVPicture pict;

    if(bitmap != NULL){

	    if ((ret = AndroidBitmap_getInfo(env, bitmap, &info)) < 0) {
			LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret);
			return -1;
		}


        // Convert the YUV to RGB565, init sw coverter context
        if(img_convert_ctx == NULL) {
            int w = pCodecCtx->width;
            int h = pCodecCtx->height;
            img_convert_ctx = sws_getContext(w, h,
                pCodecCtx->pix_fmt, info.width, info.height,
                PIX_FMT_RGB565LE, SWS_BICUBIC, NULL, NULL, NULL);

            if(img_convert_ctx == NULL) {
                LOGE("Cannot initialize the conversion context!\n");
                return -1;
            }
        }

		if ((ret = AndroidBitmap_lockPixels(env, bitmap, &pixels)) < 0) {
			LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
		}
        //int sws_scale(SwsContext *c, const uint8_t* const src[], const int srcStride[], int srcSliceY,
        //      int srcSliceH, uint8_t* const dst[], const int dstStride[])

        pict.data[0] = (uint8_t*)pixels;
        pict.linesize[0] = info.stride;

        LOGD("start swscale!");
        sws_scale(img_convert_ctx, pFrame->data,  pFrame->linesize,
            0, pCodecCtx->height, pict.data, pict.linesize);

        AndroidBitmap_unlockPixels(env, bitmap);

    } else {
        LOGE("error! cannot get bitmap object!");
    }
	return 0;
}

int jni_ffmpeg_decode(JNIEnv * env, jobject  obj){
	int frameFinished = 0;

	while(av_read_frame(pFormatCtx, &packet)>=0) {
	  	// Is this a packet from the video stream?
	  	if(packet.stream_index==videoStream) {
			// Decode video frame
			avcodec_decode_video(pCodecCtx, pFrame, &frameFinished, packet.data, packet.size);
			// Did we get a video frame?
			if(frameFinished) {


				av_free_packet(&packet);
				LOGD("finished 1 frame: format=%x PIX_FMT_RGB565LE=%x (%d %d)", pCodecCtx->pix_fmt, PIX_FMT_RGB565LE, pCodecCtx->width, pCodecCtx->height);
				return 0;
			}
		}

		// Free the packet that was allocated by av_read_frame
		av_free_packet(&packet);
	}
	return -1;
}

static const JNINativeMethod gControlMethods[] = {
	{"Native_initFfmpeg", 		"()I", 	(void*)jni_ffmpeg_init},
	{"Native_setupFile", 		"(Ljava/lang/String;)I", (void*)jni_ffmpeg_setupFile },
	{"Native_startPlay", 		"()I", 	(void*)jni_ffmpeg_startPlay },
	{"Native_cleanFfmpeg", 		"()I", 	(void*)jni_ffmpeg_cleanup },
	{"Native_stopPlay", 		"()I", 	(void*)jni_ffmpeg_stop },
	{"Native_updateSurface", 	"(Ltest/freshui/ffmpeg/PlayerView;)I", (void*)jni_ffmpeg_updateSurface},
	{"Native_decodeOneFrame", 	"()I", 	(void*)jni_ffmpeg_decode },

};


static const JNINativeMethod gViewMethods[] = {
	{"nativeViewInit",     "(Landroid/graphics/Bitmap;)I", 	(void*)player_nativeViewInit },
	{"nativeViewRender",   "(Landroid/graphics/Bitmap;)I", 	(void*)player_nativeViewRender },

};

static int _register(JNIEnv* env,
                     const char* const classname,
                     const JNINativeMethod *method,
                     int size){
   jclass clazz;
   clazz = env->FindClass(classname);

   if (clazz == NULL) {
        LOGE("Can't find class %s\n", classname);
        return -1;
    }

    /* register all the methods */
    if (env->RegisterNatives(clazz, method, size) != JNI_OK)
    {
        LOGE("Failed registering methods for %s\n", classname);
        return -1;
    }

    return 0;
}

static int registerMethods(JNIEnv* env) {

    /* look up the class */
   if( _register(env, "test/freshui/ffmpeg/PlayerView", gViewMethods, sizeof(gViewMethods)/sizeof(gViewMethods[0])) != 0)
        return -1;

    if( _register(env, "test/freshui/ffmpeg/PlayerControl", gControlMethods, sizeof(gControlMethods)/sizeof(gControlMethods[0])) != 0)
        return -1;
    /* fill out the rest of the ID cache */
    return 0;
}


// ----------------------------------------------------------------------------

/*
 * This is called by the VM when the shared library is first loaded.
 */
extern "C"  jint JNI_OnLoad(JavaVM* vm, void* reserved) {
    JNIEnv* env = NULL;
    jint result = -1;


    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        LOGE("ERROR: GetEnv failed\n");
        goto bail;
    }
    assert(env != NULL);

    if (registerMethods(env) != 0) {
        LOGE("ERROR: PlatformLibrary native registration failed\n");
        goto bail;
    }

    /* success -- return valid version number */
    result = JNI_VERSION_1_4;

bail:
    return result;
}

